package ray;

import ray.math.Matrix4;
import ray.math.Point3;
import ray.math.Vector3;


public class Ray {

    /**
     * This quantity represents a "small amount" to handle comparisons in floating point computations. It is often useful to have
     * one global value across the ray tracer that stands for a "small amount".
     */
    public static final double TINY = 1e-6;

    /** The starting point of the ray. */
    public final Point3 p = new Point3();

    /** The normalized direction in which the ray travels. */
    public final Vector3 d = new Vector3();

    /**
     * It is convenient to have a ray have a start and end t values. The start value lets the ray be offset slightly from surfaces
     * avoiding self intersection through numerical inaccuracies, and the end value lets rays be cut off at certain points (such
     * as for shadow rays).
     */

    /** Starting t value of the ray **/
    public double start;

    /** Ending t value of the ray **/
    public double end;

    /**
     * Default constructor generates a trivial ray.
     */
    public Ray() {
    }

    /**
     * The explicit constructor. This is the only constructor with any real code in it. Values should be set here, and any
     * variables that need to be calculated should be done here.
     * 
     * @param newOrigin
     *            The origin of the new ray.
     * @param newDirection
     *            The direction of the new ray.
     */
    public Ray(Point3 newOrigin, Vector3 newDirection) {
        p.set(newOrigin);
        d.set(newDirection);
    }

    public Ray(Ray copy) {
        p.set(copy.p);
        d.set(copy.d);
        start = copy.start;
        end = copy.end;
    }

    /**
     * Sets this ray with the given direction and origin.
     * 
     * @param newOrigin
     *            the new origin point
     * @param newDirection
     *            the new direction vector
     */
    public void set(Point3 newOrigin, Vector3 newDirection) {
        p.set(newOrigin);
        d.set(newDirection);
        d.normalize();
    }

    private void assertNotZero(Vector3 transformedDirection) {
        assert !(transformedDirection.x == 0 && transformedDirection.y == 0 && transformedDirection.z == 0) : "Direction is 0";
    }
    
    public void assertDirectionNotZero() {
        assertNotZero(d);
    }

    /**
     * Sets outPoint to the point on this ray t units from the origin.
     * 
     * @param outPoint
     *            the output point
     * @param t
     *            The distance along the ray.
     */
    public void evaluate(Point3 outPoint, double t) {
        outPoint.set(p);
        d.normalize();
        outPoint.scaleAdd(t, d);
    }

    /**
     * Moves the origin of the ray EPISILON units along ray. Avoids self intersection when casting rays from surfaces.
     */
    public void makeOffsetRay() {
        start = TINY;
        end = Double.POSITIVE_INFINITY;
    }

    /**
     * Offsets the ray origin by EPSILON and sets the end point of the ray to t.
     * 
     * @param newEnd
     *            the endpoint of the ray.
     */
    public void makeOffsetSegment(double newEnd) {
        start = TINY;
        end = newEnd;
    }
}